package com.nuo.backend.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.nuo.backend.common.utils.JwtUtils;
import com.nuo.backend.common.utils.R;
import com.nuo.backend.modules.security.config.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.session.ConcurrentSessionFilter;

import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * @author lcc
 * @create 2021-05-30
 * @注意 本内容仅限于dev414内部传阅，禁止外泄以及用于其他的商业目的
 */
@Slf4j
@Configuration
// TODO 开启 web 安全
@EnableWebSecurity
// TODO 开启全局使用权限注解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    private static final String[] URL_WHITELIST = {
            "/webjars/**",
            "/druid/**",
            "/storage/**",
            "/swagger/**",
            "/v2/api-docs",
            "/swagger-ui.html",
            "/swagger-resources/**",
            "/sys/**",
            "/captcha.jpg",
            "/test/**",
            "/user/**",
            "/sec/login",
            "/sec/register",
            "/sec/uuid",
            "/sec/captcha.jpg",
            "/sec/captchaStr"
    };
    @Autowired
    JwtUtils jwtUtils;
    @Autowired
    LoginFailureHandler loginFailureHandler;
    @Autowired
    LoginSuccessHandler loginSuccessHandler;
    @Autowired
    UserDetailServiceImpl userDetailService;
    @Autowired
    JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    @Autowired
    JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Autowired
    CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;

    @Autowired
    CustomUrlDecisionManager customUrlDecisionManager;


    @Bean
    JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
        // 当前的jwtFilter需要注入权限管理器
        return new JwtAuthenticationFilter(authenticationManager());
    }

    @Bean
    BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    SessionRegistryImpl sessionRegistry() {
        return new SessionRegistryImpl();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .formLogin()
                .passwordParameter("password")
                .usernameParameter("username")
                .loginProcessingUrl("/sec/login")
                .permitAll()
                .and()

                // 配置 session
                .sessionManagement()
                // 不通过Session获取SecurityContext
                // .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()

                // 配置授权拦截规则
                .authorizeRequests()
                // TODO　动态权限拦截, 会导致白名单配置失效
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(customUrlDecisionManager);
                        object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                        return object;
                    }
                })
                // TODO 允许白名单
                .antMatchers(URL_WHITELIST).permitAll()
                .anyRequest().authenticated()
                .and()

                // 异常处理器
                .exceptionHandling()
                // 未认证切面
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                // jwt出现的异常处理器
                .accessDeniedHandler(jwtAccessDeniedHandler)
                .and()

                // 验证jwt是否过期过滤器
                .addFilter(jwtAuthenticationFilter())
                // 多用户登陆时, 踢人下线(doge
                .addFilterAt(new ConcurrentSessionFilter(sessionRegistry(), event -> {
                    log.error("您已在另一台设备登录，本次登录已下线!");
                    HttpServletResponse resp = event.getResponse();
                    resp.setContentType("application/json;charset=utf-8");

                    resp.setStatus(401);

                    // 清空请求头
                    resp.setHeader(jwtUtils.getHeader(), "");

                    PrintWriter out = resp.getWriter();
                    out.write(new ObjectMapper().writeValueAsString(R.error("您已在另一台设备登录，本次登录已下线!")));
                    out.flush();
                    out.close();
                }), ConcurrentSessionFilter.class)

                // 用户名密码过滤器
                .addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)

                // 配置登出
                .logout()
                .logoutUrl("/sec/logout")
                .deleteCookies("JSESSIONID")
                .logoutSuccessHandler((req, resp, authentication) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();

                    // 清空请求头
                    resp.setHeader(jwtUtils.getHeader(), "");

                    out.write(new ObjectMapper().writeValueAsString(R.ok("登出成功!")));
                    out.flush();
                    out.close();
                })
                .permitAll();
    }

    // TODO 自定义json登录,校验 UserDetailService 中的权限和密码
    @Bean
    CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
        CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
        filter.setFilterProcessesUrl("/sec/login");
        filter.setAuthenticationSuccessHandler(loginSuccessHandler);
        filter.setAuthenticationFailureHandler(loginFailureHandler);
        filter.setAuthenticationManager(authenticationManagerBean());
        // 配置多用户登陆时, 踢人下线(doge
        ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
        sessionStrategy.setMaximumSessions(1);
        filter.setSessionAuthenticationStrategy(sessionStrategy);

        return filter;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService);
    }

}
